//
//  TagCloudView.m
//  Task Views
//
//  Created by Chris Karr on 3/3/09.
//  Copyright 2009 Chris J. Karr. All rights reserved.
//

#import "TagCloudView.h"

#import "NSButton+TextColor.h"

NSInteger areaSort (id one, id two, void * reverse)
{
	NSRect oneRect = [one rectValue];
	NSRect twoRect = [two rectValue];
	
	double oneArea = oneRect.size.width * oneRect.size.height;
	double twoArea = twoRect.size.width * twoRect.size.height;	

	if (oneArea > twoArea)
		return NSOrderedAscending;
	
	return NSOrderedDescending;
}

@implementation TagCloudView

@synthesize key;
@synthesize multiplier;
@synthesize adjustment;
@synthesize color;
@synthesize backgroundColor;
@synthesize colorField;
@synthesize mappings;
@synthesize colorStrings;	

- (BOOL) acceptsFirstResponder
{
	return YES;
}        

- (void) awakeFromNib
{
	lastSize = NSMakeRect(1, 1, 1, 1);
	
	[[self window] setAcceptsMouseMovedEvents:YES];
	
	[tasks addObserver:self forKeyPath:@"arrangedObjects" options:0 context:NULL];
	[tasks addObserver:self forKeyPath:@"selection" options:0 context:NULL];

	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	
	[defaults addObserver:self forKeyPath:@"tag_size_ln" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"tag_size_attribute" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"tag_size_multiplier" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"tag_size_adjustment" options:0 context:NULL];

	[defaults addObserver:self forKeyPath:@"tag_default_color" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"tag_color_attribute" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"color_mappings" options:0 context:NULL];

	[defaults addObserver:self forKeyPath:@"cloud_background" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"cloud_float" options:0 context:NULL];

	[self observeValueForKeyPath:nil ofObject:nil change:nil context:NULL];
}

- (void) observeValueForKeyPath: (NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context
{
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	self.key = [defaults valueForKey:@"tag_size_attribute"];
	
	self.multiplier = [defaults valueForKey:@"tag_size_multiplier"];
	if (self.multiplier == nil)
		self.multiplier = [NSNumber numberWithInteger:0];
	
	self.adjustment = [defaults valueForKey:@"tag_size_adjustment"];
	if (self.adjustment == nil)
		self.adjustment = [NSNumber numberWithInteger:0];
	
	self.color = [NSColor lightGrayColor];
	NSData * colorData = [defaults valueForKey:@"tag_default_color"];
	if (colorData != nil)
		self.color = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];
	
	self.colorField = [defaults valueForKey:@"tag_color_attribute"];
	if (self.colorField == nil)
		self.colorField = @"name";
	
	self.mappings = [defaults objectForKey:@"color_mappings"];
	if (self.mappings == nil)
		self.mappings = [NSDictionary dictionary];
	
	self.colorStrings = [mappings allKeys];
	
	useLog = [defaults boolForKey:@"tag_size_ln"];
	
	NSData * background = [defaults valueForKey:@"cloud_background"];
	
	if (background == nil)
		self.backgroundColor = [NSColor blackColor];
	else
		self.backgroundColor = [NSKeyedUnarchiver unarchiveObjectWithData:background];
	
	[self setNeedsDisplay:YES];

	NSNumber * floatTop = [[NSUserDefaults standardUserDefaults] valueForKey:@"cloud_float"];
	
	if (floatTop != nil && [floatTop boolValue])
		[[self window] setLevel:NSStatusWindowLevel];
	else
		[[self window] setLevel:NSNormalWindowLevel];
}

- (NSValue *) bestRectInValues:(NSArray *) values forSize:(NSSize) size
{
	if ([values count] == 0)
		return nil;
	
	for (NSValue * val in values)
	{
		NSRect rect = [val rectValue];
		
		if (rect.size.width >= size.width && rect.size.height >= size.height)
			return val;
	}
	
	return nil;
}

- (NSArray *) shatterRect:(NSRect) container withRect:(NSRect) hole
{
	NSMutableArray * remainders = [NSMutableArray array];

	NSSize wide = NSMakeSize(container.size.width, ((container.size.height - hole.size.height) / 2));
	NSSize tall = NSMakeSize(((container.size.width - hole.size.width) / 2), container.size.height);

	if ((wide.width * wide.height) < (tall.width * tall.height)) // Use tall blocks
	{
		NSRect left = NSMakeRect(container.origin.x, container.origin.y, tall.width, tall.height);
		NSRect right = NSMakeRect((container.origin.x + container.size.width) - tall.width, container.origin.y, 
								  tall.width, tall.height);
		NSRect bottom = NSMakeRect(container.origin.x + tall.width, container.origin.y, 
								   hole.size.width, ((container.size.height - hole.size.height) / 2));
		NSRect top = NSMakeRect(container.origin.x + tall.width, hole.origin.y + hole.size.height, 
								   hole.size.width, ((container.size.height - hole.size.height) / 2));
		
		if (left.size.width > 5 && left.size.height > 5)
			[remainders addObject:[NSValue valueWithRect:left]];
		if (right.size.width > 5 && right.size.height > 5)
			[remainders addObject:[NSValue valueWithRect:right]];
		if (top.size.width > 5 && top.size.height > 5)
			[remainders addObject:[NSValue valueWithRect:top]];
		if (bottom.size.width > 5 && bottom.size.height > 5)
			[remainders addObject:[NSValue valueWithRect:bottom]];
	}
	else
	{
		NSRect bottom = NSMakeRect(container.origin.x, container.origin.y, wide.width, wide.height);
		NSRect top = NSMakeRect(container.origin.x, hole.origin.y + hole.size.height, wide.width, wide.height);
		NSRect left = NSMakeRect(container.origin.x, container.origin.y + wide.height, 
								 ((container.size.width - hole.size.width) / 2), hole.size.height);
		NSRect right = NSMakeRect(hole.origin.x + hole.size.width, left.origin.y, 
								  ((container.size.width - hole.size.width) / 2), hole.size.height);
		
		[remainders addObject:[NSValue valueWithRect:left]];
		[remainders addObject:[NSValue valueWithRect:right]];
		[remainders addObject:[NSValue valueWithRect:top]];
		[remainders addObject:[NSValue valueWithRect:bottom]];
	}
	
	return remainders;
}

/*
 * Courtesy of Scott Thompson:
 * http://lists.apple.com/archives/quartz-dev/2008/Mar/msg00079.html
 */

- (NSSize) measureFrame:(CTFrameRef) frame forContext:(NSGraphicsContext *) cgContext
{
	CGPathRef framePath = CTFrameGetPath(frame);
	CGRect frameRect = CGPathGetBoundingBox(framePath);
	
	CFArrayRef lines = CTFrameGetLines(frame);
	CFIndex numLines = CFArrayGetCount(lines);
	
	CGFloat maxWidth = 0;
	CGFloat textHeight = 0;
	
	CFIndex lastLineIndex = numLines - 1;
	for(CFIndex index = 0; index < numLines; index++)
	{
		CGFloat ascent, descent, leading, width;
		CTLineRef line = (CTLineRef) CFArrayGetValueAtIndex(lines, index);
		width = CTLineGetTypographicBounds(line, &ascent,  &descent, &leading);
		
		if(width > maxWidth)
			maxWidth = width;
		
		if(index == lastLineIndex)
		{
			CGPoint lastLineOrigin;
			CTFrameGetLineOrigins(frame, CFRangeMake(lastLineIndex, 1), &lastLineOrigin);
			textHeight =  CGRectGetMaxY(frameRect) - lastLineOrigin.y + descent;
		}
	}
	
	return NSMakeSize(ceil(maxWidth), ceil(textHeight));
}

- (void) drawRect:(NSRect) rect 
{
	tasksVisible = 0;
	
	BOOL newTracking = NO;
	
	if (!NSEqualRects(lastSize, [self frame]))
	{
		newTracking = YES;
		lastSize = [self frame];
	}
	
	if (newTracking)
	{
		for (NSTrackingArea * area in [self trackingAreas])
			[self removeTrackingArea:area];

		NSTrackingArea * area = [[NSTrackingArea alloc] initWithRect:[self bounds] 
															 options:(NSTrackingCursorUpdate | NSTrackingActiveWhenFirstResponder) 
															   owner:self 
															userInfo:nil];
		[self addTrackingArea:area];
		[area release];
	}

	[self willChangeValueForKey:@"tasksVisible"];

	[backgroundColor setFill];
	NSBezierPath * bgPath = [NSBezierPath bezierPathWithRect:rect];
	[bgPath fill];
	
	NSMutableArray * rects = [NSMutableArray arrayWithObject:[NSValue valueWithRect:[self bounds]]];

	NSGraphicsContext * context = [[NSGraphicsContext currentContext] graphicsPort];
	CGContextSetTextMatrix((CGContextRef) context, CGAffineTransformIdentity);

	double sum = 0;
	unsigned int count = 0;
	
	for (NSDictionary * task in [tasks arrangedObjects])
	{
		NSObject * value = [task valueForKey:key];

		if ([value isKindOfClass:[NSDate class]])
		{
			sum += [((NSDate *) value) timeIntervalSinceNow];
			count = count + 1;
		}
	}
	
	if (count < 1)
		count = 1;
	
	double dateAverage = round((((sum / (double) count) / 60 ) / 60) / 24);
	
	NSArray * selected = [tasks selectedObjects];
	
	for (NSDictionary * task in [tasks arrangedObjects])
	{
		NSObject * value = [task valueForKey:key];
		NSNumber * base = nil;
		if ([value isKindOfClass:[NSNumber class]])
			base = (NSNumber *) value;
		else if ([value isKindOfClass:[NSDate class]])
		{
			NSTimeInterval dateDelta = round((([((NSDate *) value) timeIntervalSinceNow] / 60) / 60) / 24);
			base = [NSNumber numberWithDouble:(dateAverage - dateDelta)];
		}
		else if ([value description] != nil)
		{
			NSScanner * scanner = [NSScanner scannerWithString:[value description]];
			
			double d;
			if (![scanner scanDouble:&d])
				d = 1;
			base = [NSNumber numberWithDouble:d];
		}
		
		float baseFloat = [base floatValue];
		
		if (useLog)
		{
			float absLog = log(abs(baseFloat));
			
			if (baseFloat < 0)
				baseFloat = 0 - absLog;
			else
				baseFloat = absLog;
		}
		
		float fontSize = (baseFloat * [multiplier floatValue]) + [adjustment floatValue];
		
		if (fontSize < 0)
			fontSize = 0;
		
		fontSize = 6 + floorf(fontSize);

		NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:[[task valueForKey:@"name"] description]];
		NSFont * font = [NSFont fontWithName:@"Lucida Grande" size:fontSize];
		[string addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [string length])];

		NSObject * colorValue = [task valueForKey:colorField];
		
		if (colorValue != nil)
		{
			NSString * superString = [colorValue description];
			
			NSColor * myColor = nil;
			
			for (NSString * match in colorStrings)
			{
				if (myColor == nil)
				{
					if ([superString rangeOfString:match].location != NSNotFound)
						myColor = [NSKeyedUnarchiver unarchiveObjectWithData:[mappings valueForKey:match]];
				}
				
				if (myColor != nil)
					break;
			}
			
			if ([selected containsObject:task])
				[string addAttribute:NSForegroundColorAttributeName value:[NSColor whiteColor] range:NSMakeRange(0, [string length])];
			else if (myColor != nil)
				[string addAttribute:NSForegroundColorAttributeName value:myColor range:NSMakeRange(0, [string length])];
			else
				[string addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(0, [string length])];
		}
		else
			[string addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(0, [string length])];
		
		CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef) string);

		CGMutablePathRef path = CGPathCreateMutable();
		NSRect bounds = NSMakeRect(0, 0, 20480, 20480);
		CGPathAddRect(path, NULL, NSRectToCGRect(bounds));
	
		CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
		CFRelease(path);
		
		NSSize size = [self measureFrame:frame forContext:context];
	
		NSValue * bestValue = [self bestRectInValues:rects forSize:size];
		
		if (bestValue != nil)
		{
			NSRect bestRect = [bestValue rectValue];
			
			NSPoint center = NSMakePoint((bestRect.origin.x + (bestRect.size.width / 2)), 
										 (bestRect.origin.y + (bestRect.size.height / 2)));
	
			NSPoint loc = NSMakePoint(center.x - (size.width / 2), center.y - (size.height / 2));

			CGMutablePathRef newPath = CGPathCreateMutable();
			NSRect newBounds = NSMakeRect(loc.x, loc.y, size.width, size.height);
			CGPathAddRect(newPath, NULL, NSRectToCGRect(newBounds));
			CTFrameRef newFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), newPath, NULL);
			CFRelease(newPath);
			
			if (newTracking)
			{
				NSTrackingArea * area = [[NSTrackingArea alloc] initWithRect:newBounds 
																	 options:(NSTrackingCursorUpdate | NSTrackingActiveWhenFirstResponder) 
																	   owner:self 
																	userInfo:task];
				[self addTrackingArea:area];
				[area release];
			}
			
			/* [[NSColor darkGrayColor] setFill];
			bgPath = [NSBezierPath bezierPathWithRect:newBounds];
			[bgPath fill]; */
			
			CTFrameDraw(newFrame, (CGContextRef) context);
			
			NSArray * remainders = [self shatterRect:bestRect withRect:newBounds];
			
			[rects removeObject:bestValue];
			[rects addObjectsFromArray:remainders];

			// [rects sortUsingFunction:areaSort context:NULL];
			tasksVisible = tasksVisible + 1;
		}

		CFRelease(framesetter);
		CFRelease(frame);
		
		[string release];
	}

	[self didChangeValueForKey:@"tasksVisible"];
}

- (NSNumber *) tasksVisible
{
	return [NSNumber numberWithUnsignedInteger:tasksVisible];
}

- (IBAction) settings:(id) sender
{
	[NSApp beginSheet:settings modalForWindow:[self window] modalDelegate:self didEndSelector:nil contextInfo:NULL];
}

- (IBAction) closeSettings:(id) sender;
{
	[NSApp endSheet:settings];
	[settings orderOut:self];
}

- (IBAction) addColor:(id) sender
{
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	
	NSString * string = [substring stringValue];
	NSColor * mapColor = [colorWell color];
	
	NSMutableDictionary * newMappings = [NSMutableDictionary dictionaryWithDictionary:mappings];
	[newMappings setValue:[NSKeyedArchiver archivedDataWithRootObject:mapColor] forKey:string];
	
	[defaults setObject:newMappings forKey:@"color_mappings"];
}

- (void)cursorUpdate:(NSEvent *)event
{
	BOOL hand = NO;

	if ([event userData] != nil)
	{
		if ([self mouse:[event locationInWindow] inRect:[[event trackingArea] rect]])
			hand = YES;
	}
	
	if (hand)
		[[NSCursor pointingHandCursor] set];
	else
		[[NSCursor arrowCursor] set];

}

- (void) mouseDown:(NSEvent *) theEvent
{
	for (NSTrackingArea * area in [self trackingAreas])
	{
		if ([area userInfo] != nil && [self mouse:[theEvent locationInWindow] inRect:[area rect]])
		{
			[tasks setSelectedObjects:[NSArray arrayWithObject:[area userInfo]]];
			
			if ([theEvent clickCount] > 1)
				[taskManager editTask:self];
			
			return;
		}
	}
}

@end
